﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Reflection;
using System.Text;
using System.Web;

namespace Sherwood.Configuration
{
    /// <summary>
    /// Represents a config repository that reads config files directly from disk.
    /// </summary>
    public class ConfigFileRepository : IConfigRepository
    {
        ///<summary>
        /// Get or set the root folder of the application. Is by default the folder where the web application is running (if HttpContext is available)
        /// or the location of the entry started process.
        ///</summary>
        public static string ApplicationFolder { get; private set; }

        static ConfigFileRepository()
        {
            if (HttpContext.Current != null)
            {
                ApplicationFolder = HttpContext.Current.Server.MapPath("/");
            }
            else
            {
                Assembly entryAssembly = Assembly.GetEntryAssembly();
                if (entryAssembly != null && entryAssembly.Location != null)
                    ApplicationFolder = System.IO.Path.GetDirectoryName(entryAssembly.Location);
            }
        }

        public ConfigFileRepository(string path)
        {
            if (!System.IO.Path.IsPathRooted(path))
            {
                if (HttpContext.Current != null)
                {
                    if (!path.StartsWith("~"))
                        path = "~/" + path.Replace('\\', '/');
                    path = HttpContext.Current.Server.MapPath(path);
                }
                else
                {
                    Assembly entryAssembly = Assembly.GetEntryAssembly();
                    if (entryAssembly != null && entryAssembly.Location != null)
                    {
                        string root = System.IO.Path.GetDirectoryName(entryAssembly.Location);
                        path = System.IO.Path.Combine(root, path);
                    }
                    else
                        throw new InvalidOperationException("Could not find virtual path " + path + ".");
                }
            }
            Path = path;
        }

        /// <summary>
        /// A directory used as default directory for various configuration files
        /// </summary>
        public string Path { get; private set; }

        #region IConfigRepository Members

        public TextReader CreateTextReaderSafe(string fileName)
        {
            fileName = GetAbsoluteFileName(fileName);
            return File.Exists(fileName) ? new StreamReader(fileName, Encoding.UTF8) : null;
        }

        public TextWriter CreateTextWriter(string fileName)
        {
            return new StreamWriter(GetAbsoluteFileName(fileName), false, Encoding.UTF8);
        }

        public IEnumerable<string> GetFileNames(string path, string searchPattern)
        {
            return Directory.GetFiles(GetAbsoluteFileName(path), searchPattern);
        }

        public bool IsPathValid(string path)
        {
            var fileName = GetAbsoluteFileName(path);
            return Directory.Exists(fileName) || File.Exists(fileName);
        }

        #endregion

        /// <summary>
        /// Returns the absolute path of the configuration file. May be relative to <see cref="Path"/> or an absolute path.
        /// </summary>
        /// <param name="fileName">The absolute or relative filename of the requested config file</param>
        /// <exception cref="ConfigurationErrorsException">ConfigDirectory is not specified</exception>
        /// <exception cref="ArgumentNullException">fileName</exception>
        public string GetAbsoluteFileName(string fileName)
        {
            if (string.IsNullOrEmpty(fileName))
                throw new ArgumentNullException("fileName");
            if (System.IO.Path.IsPathRooted(fileName))
                return System.IO.Path.GetFullPath(fileName);

            if (string.IsNullOrEmpty(Path))
                throw new ConfigurationErrorsException("ConfigDirectory is not specified");

            return System.IO.Path.Combine(Path, fileName);
        }
    }
}